<?php
/**
 * @author jonathan <jo.link.noir@gmail.com>
 * @version 1.0: jo_link_noir
 * @date 27-05-2010
 */

class U1_Move_Units_Exception extends SW_Exception
{
}

/**
 * Gére les déplacements d'une armée. La chaine des mouvements lors de l'envoie ce réduit au 1er. L'ordre de retour est calculé à l'arrivé (update)
 */
class U1_Move_Units extends SW_Move_Units
{
	/** @var array {int, int} : consommation d'hydro et d'azarite*/
	public $fuel = array(null, null);

	/**
	 * Envoie les vaisseaux, si raté lance une exception
	 * @param int $timeRef : toujours égal à $_SERVER['REQUEST_TIME']
	 * @return int : id du mouvement
	 * @exceptions U1_Move_Units_Exception
		- 'Il n\'est pas possible de vous s\'attaquer', 1
		- 'La planète n\'existe pas', 2
		- 'Colonisé ne fonctione que sur des planètes non colonisée', 3
		- 'Il n\'y a pas de vaisseaux séléctionnés', 4
		- 'Le carburant est insuffisant ($f[0] $name[2], $f[1] $name[3])', 5
		- 'Seuls les sondes peuvent être envoyées pour espionner', 6
		- 'Des ressources doivent être envoyées lors d\'un transport', 7
		- 'Il faut un vaisseau de colonisation pour pouvoir coloniser', 8
		- 'Cette planète n\'appartient à personne', 9
	 */
	public function send($timeRef = null)
	{
		$pointEnd = $this->move->getEndPoint();
		$pointStart = $this->move->getStartPoint();
		if ($pointEnd === $pointStart)
			return false;
		$timeRef = $_SERVER['REQUEST_TIME'];
		$isAttack = ($this->move instanceof U1_Move_Attack);

		$planetStart = U1_DAO_Planet::selectId($pointStart);
		//Récupère l'objet planète contenant $planetStart
		$planet = U1_Planet::factoryContainsPlanetElement($planetStart);

		$army = $planet->getInstance(U1_Army::INDEX, $pointStart);
		if ($this->army->limitedUnits($army->getUnits())->isEmpty())
			throw new U1_Move_Units_Exception('Il n\'y a pas de vaisseaux séléctionnés', 4);

		//vérifie la compatibilité mouvement/destination
		$order = $this->move->getOrderName();
		if ($isAttack)
		{
			if ($planet->exists($pointEnd))
				throw new U1_Move_Units_Exception('Il n\'est pas possible de vous s\'attaquer', 1);

			if ($order === 'spy' && !($this->army->offsetExists(4) && $this->army->count() === 1))
				throw new U1_Move_Units_Exception('Seuls les sondes peuvent être envoyées pour espionner', 6);

			$transportResource = null;
		}
		//s'il n'y a pas de ressource pour les transports ($transportResource est utilisé plus loin)
		else if (!($transportResource = $this->move->getRemovable('U1_Removable_Resource')))
		{
			if ($transportResource->isEmpty())
			{
				if ($order === 'transport')
					throw new U1_Move_Units_Exception('Des ressources doivent être envoyées lors d\'un transport', 7);
				else
					$transportResource = null;
			}
		}

		if (!($planetEnd = U1_DAO_Planet::selectId($pointEnd)))
			throw new U1_Move_Units_Exception('La planète n\'existe pas', 2);

		$playerTarget = $planetEnd->getRefUser();
		//elle est non colonisé et l'ordre et différent de coloniser
		if ($order === 'colonize')
		{
			if ($playerTarget)
				throw new U1_Move_Units_Exception('Colonisé ne fonctione que sur des planètes non colonisée', 3);

			$countPlanet = $planet->count();
			if (!($countPlanet < 13 && $this->army->offsetExists(13) || $countPlanet < 21 && $this->army->offsetExists(14)))
				throw new U1_Move_Units_Exception('Il faut un vaisseau de colonisation pour pouvoir coloniser', 8);
		}
		else if (!$playerTarget)
			throw new U1_Move_Units_Exception('Cette planète n\'appartient à personne', 9);

		// {minSpeed, freight, {consoHydro, consoAza}, {number_unity_h, number_unity_a}}
		$infos = U1_Army::initUnitsAndGetInfos($this->army);
		if ($order === 'attack')
			$army->initStats($this->army);

		$distance = U1_Galaxy::calculateDistance(
			$planetStart->getTarget(),
			$planetEnd->getTarget()
		);
		$duration = (int)intval(1000 * $distance / $infos[0]) + 55;
		$timeEnd = $timeRef + $duration;

		//calcule de la consommation (ajoute 1H et 1A pour chaque vaisseaux qui les consommes)
		$coef = $distance * $duration / 30000;
		$infos[2][0] = ceil(($infos[2][0] * $coef) + $infos[3][0]);
		$infos[2][1] = ceil(($infos[2][1] * $coef) + $infos[3][1]);
		$this->fuel = $infos[2];

		//récupère les ressources disponible
		$resource = $planet->getInstance(U1_Resource::INDEX, $pointStart);
		$quantities = $resource->getQuantities($timeRef);

		//vérifie que le carbu est suffisant
		if ($quantities[2] < $infos[2][0] || $quantities[3] < $infos[2][1])
			throw new U1_Move_Units_Exception('Le carburant est insuffisant ('.$infos[2][0].' Hydrogène et '.$infos[2][1].' Azarite)', 5);

		if ($transportResource)
		{
			$quantities[2] -= $this->fuel[0];
			$quantities[3] -= $this->fuel[1];
			//récupère les ressources qui vont être envoyées
			$transportResource = $transportResource->getQuantitiesAndFilter($quantities, $infos[1]);
		}

		$id = U1_DAO_Move::addMove($planet->getPlayer()->getId(), $playerTarget, $order, $pointStart, $pointEnd, $duration, $timeEnd, $this->army, $this->fuel, $resource, $timeRef, $transportResource);

		$this->move->setTime($timeEnd);
		return $id;
	}


	/** @var StdClass : chaine de responsabilitée*/
	static private $_army;

	/** @var int*/
	static private $_timeRef;


	static private function _place(StdClass $chain, StdClass $army)
	{
		if (!isset($chain->next) || !$chain->next)
			$chain->next = $army;
		else
		{
			$it = $chain->next;
			//met par ordre croissant en fonction du time_end
			while ($it)
			{
				if ($it->time_end >= $army->time_end)
				{
					$army->next = $chain->next;
					$chain->next = $army;
					return;
				}

				if (isset($it->next))
				{
					$chain = $it;
					$it = $it->next;
				}
				else
				{
					$it->next = $army;
					$it = null;
				}
			}
		}
	}

	static private function _exec()
	{
		$army = self::$_army->next;
		$timeEnd = microtime(true) + 2;
		while ($army)
		{
			SW_Move_Units::arrived($army->army);
			if (($move = $army->army->getMove()) && $move->getTime() <= self::$_timeRef)
			{
				$o = new StdClass;
				$o->id = $army->id;
				$o->army = $army->army;
				$o->time_end = $move->getTime();
				self::_place($army, $o);
			}

			$army = (isset($army->next) && microtime(true) < $timeEnd)
				? $army->next
				: null;
		}
	}

	/**
	 * Termine les mouvements arrivés
	 * @param int $timeRef = null : récupère tout les mouvements terminé avant le temps présicé. Si null $timeRef = $_SERVER['REQUEST_TIME']
	 * @param int $limit = 35 : limite de résultat
	 * @exception RangeException
	 */
	static public function processArrived($timeRef = null, $limit = 35)
	{
		self::$_timeRef = $timeRef ? $timeRef : $_SERVER['REQUEST_TIME'];
		$army = self::$_army = new StdClass;

		//enregistre les mouvement dans une chaîne
		foreach (U1_DAO_Move::selectArrived(self::$_timeRef, $limit) as $moveArmyInfo)
		{
			$army->next = $moveArmyInfo;
			$army = $moveArmyInfo;
		}

		if (!isset(self::$_army->next))
			return;

		$exception = null;
		//on va bloquer le script pour qu'un seul fasse le traitement à la fois
		if ($fp = fopen(dirname(__FILE__).'/flock', 'r+'))
		{
			if (flock($fp, LOCK_EX | LOCK_NB))
			{
				try
				{
					SW_Race::init(U1_Race::getRace());
					SW_DAO::getDb()->beginTransaction();
					self::_exec();
					SW_DAO::getDb()->commit();
				}
				catch (Exception $exception)
				{
					SW_DAO::getDb()->rollBack();
				}

				flock($fp, LOCK_UN);
			}
			else
			{
				fclose($fp);
				throw new RangeException('Impossible de vérouillage');
			}
			fclose($fp);
		}

		if ($exception)
			throw $exception;
	}
}